#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>

#include "modbus_api.h"
#include "mbport_network.h"
#include "mbport_serial.h"

#define MB_TAB_BITS_SIZES           (10)
#define MB_TAB_INPUT_BITS_SIZES     (10)
#define MB_TAB_INPUT_REGS_SIZES     (10)
#define MB_TAB_REGS_SIZES           (10)
/* Modbus registers */
static mb_uint8_t tab_bits_array[MB_TAB_BITS_SIZES];
static mb_uint8_t tab_input_bits_array[MB_TAB_INPUT_BITS_SIZES];
static mb_uint16_t tab_input_regs[MB_TAB_INPUT_REGS_SIZES];
static mb_uint16_t tab_regs[MB_TAB_REGS_SIZES];
static modbus_mapping_t regs_map =
{
    .nb_bits  = MB_TAB_BITS_SIZES,
    .start_bits = 0,
    .tab_bits = tab_bits_array,

	.nb_input_bits = MB_TAB_INPUT_BITS_SIZES,
    .start_input_bits = 0,
    .tab_input_bits = tab_input_bits_array,

	.nb_input_registers = MB_TAB_INPUT_REGS_SIZES,
    .start_input_registers = 0,
    .tab_input_registers = tab_input_regs,

    .nb_registers = MB_TAB_REGS_SIZES,
    .start_registers = 0,
    .tab_registers = tab_regs,
};


int run_state = 0;

DWORD WINAPI MbMasterThread(LPVOID p)
{
    mb_uint16_t idx;
    mb_uint16_t rsp_buffer[512];
    modbus_t *mb = (modbus_t *)p;

	while (run_state != 0)
    {
        modbus_read_holding_regs(mb, 0x0000, 10, rsp_buffer);

        for (idx = 0; idx < 10; idx++)
        {
            printf("0x%04X ", rsp_buffer[idx]);
        }
        printf("\n");
        Sleep(3000);
    }
	return 0;
}

DWORD WINAPI MbSlaveThread(LPVOID p)
{
    mb_uint8_t slaveid;
    mb_uint8_t *frame;
    mb_uint16_t length;
    modbus_t *mb = (modbus_t *)p;

    modbus_set_mapping(mb, &regs_map);

	while (run_state != 0)
    {
        if (modbus_recv(mb, &slaveid, &frame, &length) == MB_ENOERR)
        {
            modbus_replay(mb, slaveid, frame, length);
        }
    }
	return 0;
}


static void version(void)
{
	printf("Modbus Tool\n");
	printf("Version %s build %s\n", MODBUS_VERSION_STRING, __DATE__);
}


static void help(void)
{
    printf("Usage: mb <side> <mode> <port-cfg> <id>\n");
	printf("Arguments:\n");
	printf("  side         The Network side, Master = 'm/M',Slave = 's/S'\n");
	printf("  mode         Connection mode:\n");
	printf("                   'rtu/RTU', 'ascii/ASCII', 'tcp/TCP', 'udp/UDP'\n");
	printf("                   'rtu-tcp/RTU-TCP', 'rtu-udp/RTU-UDP'\n");
	printf("                   'ascii-tcp/ASCII-TCP', 'ascii-udp/ASCII-UDP'\n");
	printf("  port-cfg     Port configurate: \n");
	printf("                   Serial - <port>:<baud>:<databits>:<parity>:<stopbits>\n");
	printf("                            port       Number of port, '1/2/3/4...'\n");
	printf("                            baud       Baud rate, '4800/9600/19200/115200...'\n");
	printf("                            databits   Data bits, '8/9'\n");
	printf("                            parity     None = '0', ODD = '1', EVEN = '2'\n");
	printf("                            stopbits   Stop bits, '1/2'\n");
	printf("                   Ethernet - <ip_addr>:<port>\n");
	printf("                            ip_addr    Server IP address, such as \"192.168.1.1\"\n");
	printf("                            port       The port number, default 502\n");
	printf("  id           Device identifier, 1 <= id <= 247\n");
	printf("\n");
    printf("       mb [opt]\n");
	printf("Options:\n");
	printf("  -v          The Modbus library version\n");
	printf("  --help      Help with software use\n");
}

int main(int argc, char *argv[])
{
    int ch;
    int side;
    int mode;
    int port;
    modbus_t *mb;

    HANDLE hThread;
	DWORD  threadId;


	if (strcmp(argv[1], "--help") == 0)
    {
        goto __help;
    }
    else if ((strcmp(argv[1], "-v") == 0) ||
             (strcmp(argv[1], "--Version") == 0))
    {
        version();
        goto __exit;
    }
    else if (argc < 5)
    {
         goto __help;
    }

	/* Side */
    if ((strcmp(argv[1], "m") == 0) || (strcmp(argv[1], "M") == 0))
    {
        side = 1;   /* Master */
    }
    else if ((strcmp(argv[1], "s") == 0) || (strcmp(argv[1], "S") == 0))
    {
        side = 2;   /* Slave */
    }
    else
    {
        goto __help;
    }

    /* Mode */
    if ((strcmp(argv[2], "rtu") == 0) || (strcmp(argv[2], "RTU") == 0))
    {
        port = 1;   /* Serial port */
        mode = MODBUS_MODE_RTU;
    }
    else if ((strcmp(argv[2], "ascii") == 0) || (strcmp(argv[2], "ASCII") == 0))
    {
        port = 1;   /* Serial port */
        mode = MODBUS_MODE_ASCII;
    }
    else if ((strcmp(argv[2], "tcp") == 0) || (strcmp(argv[2], "TCP") == 0))
    {
        port = 2;   /* Network tcp port */
        mode = MODBUS_MODE_NONE;
    }
    else if ((strcmp(argv[2], "udp") == 0) || (strcmp(argv[2], "UDP") == 0))
    {
        port = 3;   /* Network udp port */
        mode = MODBUS_MODE_NONE;
    }
    else if((strcmp(argv[2], "rtu-tcp") == 0) || (strcmp(argv[2], "RTU-TCP") == 0))
    {
        port = 2;   /* Network tcp port */
        mode = MODBUS_MODE_RTU;
    }
    else if((strcmp(argv[2], "rtu-udp") == 0) || (strcmp(argv[2], "RTU-UDP") == 0))
    {
        port = 3;   /* Network tcp port */
        mode = MODBUS_MODE_RTU;
    }
    else if((strcmp(argv[2], "ascii-tcp") == 0) || (strcmp(argv[2], "ASCII-TCP") == 0))
    {
        port = 2;   /* Network tcp port */
        mode = MODBUS_MODE_ASCII;
    }
    else if((strcmp(argv[2], "ascii-udp") == 0) || (strcmp(argv[2], "ASCII-UDP") == 0))
    {
        port = 3;   /* Network udp port */
        mode = MODBUS_MODE_ASCII;
    }
    else
    {
        goto __help;
    }

    if (port == 1)
    {
        mbport_sercfg_t sercfg;
        int port, baud, databits, parity, stopbits;
        if (sscanf(argv[3], "%d:%d:%d:%d:%d", &port, &baud, &databits, &parity, &stopbits) != 5)
        {
            goto __help;
        }

        sercfg.baud = baud;
        sercfg.data_bit = databits;
        sercfg.parity = parity;
        sercfg.stop_bit = stopbits;

        /* Create a Modbus object in RTU mode, the connection port is serial port. */
        mb = modbus_create(mode, mbport_serial_create(port));
        if (mb == MB_NULL)
            goto __exit;

        if (modbus_control(mb, MODBUS_CTRL_WRITE, &sercfg) != MB_ENOERR)
        {
            modbus_destroy(mb);
            goto __exit;
        }
    }
    else if ((port == 2) || (port == 3))
    {
        char *pos;
        void *mbport;
        int svr_port;
        char ipaddr[50] = { 0 };

        mbport_net_proto_t proto;
        mbport_netcfg_t netcfg;

        pos = strchr(argv[3], ':');
        if (pos == NULL)
            goto __help;

        svr_port = atoi((const char *)(pos + 1));
        memcpy(ipaddr, argv[3], (int)(pos - argv[3]));

        printf("IP = %s, Port = %d\n", ipaddr, svr_port);

        if (port == 2)
        {
            proto = MBPORT_NET_TCP;
        }
        else if (port == 3)
        {
             proto = MBPORT_NET_UDP;
        }

        if (side == 1)
        {
            MBPORT_NET_CFG(&netcfg, MB_NULL, ipaddr,
                MB_NULL, MB_NULL, 0, svr_port, 3000);
            mbport = mbport_network_create(MBPORT_NET_CLIENT, proto);
        }
        else if (side == 2)
        {
            MBPORT_NET_CFG(&netcfg, ipaddr, MB_NULL,
                MB_NULL, MB_NULL, svr_port, 0, 3000);
            mbport = mbport_network_create(MBPORT_NET_SERVER, proto);
        }

        /* Create a Modbus object in TCP/UDP mode. */
        mb = modbus_create(mode, mbport);
        if (mb == MB_NULL)
            goto __exit;

        if (modbus_control(mb, MODBUS_CTRL_WRITE, &netcfg) != MB_ENOERR)
        {
            modbus_destroy(mb);
            goto __exit;
        }

    }

    /* Set slave identifier */
    if (modbus_set_slave(mb, atoi(argv[4])) != MB_ENOERR)
    {
        modbus_destroy(mb);
        goto __exit;
    }

    /* Open the connection */
    int i;
    for (i = 0; i < 5; i++)
    {
        if (modbus_connect(mb) == MB_ENOERR)
            break;
    }
    if (i == 5)
    {
        modbus_destroy(mb);
        goto __exit;
    }

    if (side == 1)
    {
        hThread = CreateThread(NULL, 0, MbMasterThread, mb, 0, &threadId);
    }
    else if (side == 2)
    {
        hThread = CreateThread(NULL, 0, MbSlaveThread, mb, 0, &threadId);
    }

    run_state = 1;

    printf("Type 'q' for quit or 'h' for help!\r\n");

    while (1)
    {
        ch = getc(stdin);

        switch (ch)
        {
            case 'q':
                run_state = 0;
                DWORD dwRet = WaitForSingleObject(hThread, 5000);
                if (dwRet == WAIT_OBJECT_0)
                {
                    printf("Exit.");
                }
                else if (dwRet == WAIT_TIMEOUT)
                {
                    printf("Timeout.\n");
                }
                else if (dwRet == WAIT_ABANDONED)
                {
                    printf("Abandoned.\n");
                }
                CloseHandle(hThread);
                goto __exit;

            case 'h':
                help();
                break;
            default:
                break;
        }
    }

__exit:
    return 0;

__help:
    help();

}


